(load "project.rkt")

(define SECTION-INFO (list))
(define INPUT-VALS (list))

#| +++++++++++ Form +++++++++++ |#

;reset: void -> void
;Resets all global variables
(define (reset)
  (set! SECTION-INFO (list))
  (set! INPUT-VALS (list)))

;run-form: form -> void
;Runs the form commands
(define (run-form form)
  (run-cmds (form-commands form)))

#| +++++++++++ List of commands +++++++++++ |#

;run-cmds: list[cmd] -> void
;Runs all the cmds in other
(define (run-cmds cmd-list)
  (for-each run-cmd cmd-list))


(define-syntax patternlist
  (syntax-rules()
    [(patternlist (instruction target ) ...)
     (lambda (input) 
       [cond 
         [(instruction input) (target input)] ...
         ])]))

;run-cmd: cmd -> void
;Checks the type of structure against a table of possible structures
(define run-cmd 
  (patternlist
   (section? start-new-section)
   (message? show-message)
   (message-var? show-message-var)
   (process-value? store-process-value)
   (process-sum-values? store-process-values-sum)
   (question? ask-question)
   (question-list? ask-question-list)
   (question-numeric-list? ask-question-numeric-list)
   (question-boolean? ask-question-boolean)
   (question-numeric? ask-question-numeric)
   (question-choice? ask-question-choice)
   (conditional? check-conditional)
   (print-sections? print-information-sections)
   ))


#| +++++++++++ Commands +++++++++++ |#

;get-value: key -> value
;Returns the value of the key from the table, throws error if not found
(define (get-value key) 
  (let [(val (findf 
              (lambda (input) (string=? (data-key input) (string-downcase key)))
              INPUT-VALS))]
    (cond [(boolean? val)
           (error (format "Could not find key: ~a" key))]
          [else
           (data-value val)])))

;get-values: list[key] -> list[value]
;Gets a list of given keys
(define (get-values key-list)
  (map get-value key-list))

;put-value: key value -> void
;Puts the key into the table of values
(define (put-value key value)
  (set! INPUT-VALS
        (cons (make-data (string-downcase key) value)
              (filter (lambda (input) 
                        (not (string=? 
                              (data-key input) 
                              (string-downcase key)))) 
                      INPUT-VALS)))
  (add-to-session key))


#| +++++++++++ Section +++++++++++ |#  

;start-new-section: section -> void
;Printst he current information about a section
;Also sets the current working section to it
(define (start-new-section section)
  (printf "--------- ~a: ~a ----------~n" (section-name section) (section-description section))
  (set! SECTION-INFO
        (cons 
         (make-section-info (section-name section) empty)
         SECTION-INFO)))


;add-to-session: key -> void
;Puts a key into the list of current items in the section
(define (add-to-session key)
  (if (not (empty? SECTION-INFO))
      (set! SECTION-INFO
            (cons
             (make-section-info
              (section-info-name (first SECTION-INFO))
              (cons key (section-info-keylist (first SECTION-INFO))))
             (rest SECTION-INFO)))))        


#| ----------- END OF Section ----------- |#

;show-message: string -> void
;Outputs the message into the window
(define (show-message message)
  (printf "~s~n" (message-input message)))

;show-message-var: string key -> void
;Outsputs the message with a variable at the end
(define (show-message-var message)
  (printf "~s~s~n" (message-var-input message) (get-value (message-var-key message)) ))

;store-process-value: process-value
;Stores a value returned from the procedure into the given key
(define (store-process-value process)
  (put-value (process-value-key process)
             ((process-value-func process))))


;store-process-values-sum: process-sum-values
;Stores the sum of the keys into the given value
(define (store-process-values-sum process)
  (put-value (process-sum-values-key process)
             (foldr + 0 (get-values (process-sum-values-key-list process)))))

#| +++++++++++ Question +++++++++++ |#

;await-input: procedure -> value
;Keeps reading the value until the validator agrres with the input
(define (await-input validator)
  (let [(input (read))]
    (or (and (validator input) input)
        (await-input validator))))

;multiple-await-input-ask: validator -> value
;Keeps asking for more input until the input 'end is given
(define (multiple-await-input-ask validator)
  (let [(input (await-input validator))]
    (cond [(symbol=? input 'end)
           empty]
          [else 
           (cons input (multiple-await-input-ask validator))])))

;multiple-await-input: string procedure -> list[value]
;Asks the question and keep collecting the inputs
(define (multiple-await-input question validator)
  (printf "~a~n" question)
  (printf "Enter 'end' when you are finished.~n")
  (multiple-await-input-ask validator))


;ask-question: question -> void
;Asks the question
(define (ask-question question)
  (printf "~a~n" (question-question question))
  (put-value (question-key question)
             (await-input symbol?)))

;ask-question-list question-list -> void
;Asks the question and stores a list of values
(define (ask-question-list question)
  (put-value 
   (question-list-key question)
   (multiple-await-input (question-list-question question) symbol?)))

;ask-question-numeric-list question-numeric-list -> void
;Asks the question and stores a list of numeric values
(define (ask-question-numeric-list question)
  (put-value 
   (question-numeric-list-key process)
   (multiple-await-input (question-numeric-list-question question) number?)))


;Global variables to compare values if they are true
(define VALID-TRUE-BOOLEANS '(true y t yes))
(define VALID-FALSE-BOOLEANS '(false n f no))
(define VALID-BOOLEANS (append VALID-TRUE-BOOLEANS VALID-FALSE-BOOLEANS))

;valid-boolean: mixed -> boolean
;Check if the input is a valid string boolean
(define (valid-boolean b)
  (or (boolean? b) (not (boolean? (member b VALID-BOOLEANS)))))

;string->boolean: mixed -> boolean
;Converts a string to a boolean given the global variables
(define (string->boolean b)
  (cond [(or (eq? b #t) (member b VALID-TRUE-BOOLEANS))
         #t]
        [else
         #f]))

;ask-question-boolean: question-boolean -> void
;Asks a question that can only intake a boolean to the key
(define (ask-question-boolean question)
  (printf "~a: [y/n]~n" (question-boolean-question question))
  (put-value (question-boolean-key question)
             (string->boolean (await-input valid-boolean))))


;ask-question-numeric: question-numeric -> void
;Asks a question that only intakes numeric answers
(define (ask-question-numeric question)
  (printf "~a~n" (question-numeric-question question))
  (put-value (question-numeric-key question)
             (await-input number?)))


;print-choices: list[string] index = 0 -> void
;Prints the list of options to the user
(define (print-choices item-list index)
  (cond [(empty? item-list) null]
        [else 
         (printf "	~a. ~a~n" index (first item-list))
         (print-choices (rest item-list) (+ 1 index))
         ]))

;in-range: number number -> procedure
;A procedure that checks if the input is in range
(define (in-range min max)
  (lambda ( input )
    (and ( number? input ) ( >= input min ) (<= input max ))))

;take: list number -> value
;Check documentation why this function should exist, and I should not be redefining it
(define (take item-list count)
  (cond [(= count 1)
         (first item-list)]
        [else
         (take (rest item-list) (- count 1))]))

;ask-question-choice: question-choice -> void
;Asks the multiple choice question to the user
(define (ask-question-choice question)
  (printf "~a~n" (question-choice-question question))
  [let* [(options (question-choice-options question))]
    (print-choices options 1)
    (put-value (question-choice-key question)
               (take options
                     (await-input (in-range 0 (length options)))))])


;run-multi: mixed -> void
;Checks if the input is a form, and  runs it, otherwise execute the commands
(define (run-multi input)
  (if (form? input)
      (run-form input)
      (run-cmds input)))

;check-conditional: conditional -> void
;Checks if the conditional is true and runs a list of commands
(define (check-conditional cond)
  (if ((conditional-func cond))
      (run-multi (conditional-cmd-list cond))
      (run-multi (conditional-f-cmd-list cond))))

;input-length: key -> number
;Returns the number of items in the list
(define (input-length key)
  (length (get-value key)))

;print-question-item: string -> void
;Prints the item from a list into the screen
(define (print-question-item item)
  (printf "		~a~n" item ))

;print-question-info: key -> void
;Prints all the values from the key
(define (print-question-info key)
  (let [(val (get-value key))]
    (cond [(cons? val)
           (printf "	~a:~n" key )
           (for-each print-question-item val)
           ]
          [else
           (printf "	~a:	~a~n" key val)])))

;print-information-section: section -> void
;Prints all the items from the section
(define (print-information-section info)
  (printf "~a: ~n" (section-info-name info))
  (for-each print-question-info (reverse (section-info-keylist info)))
  (printf "~n"))

;print-information-sections: print-sections -> void
;Prints EVERYTHING.
(define (print-information-sections info)
  (for-each print-information-section (reverse SECTION-INFO)))